package com.wujunshen.dumpling.database.mysql.mapping;

import com.wujunshen.dumpling.common.exception.ThrowingConsumer;
import com.wujunshen.dumpling.common.exception.ThrowingFunction;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 映射器
 *
 * @author frankwoo
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Mapping {
  /**
   * @param resultSet {@link ResultSet} 结果集
   * @param clazz 领域类型
   * @param <T> 领域泛型
   * @return 领域对象集合
   */
  @SneakyThrows
  public static <T> List<T> convertList(ResultSet resultSet, Class<T> clazz) {
    // 结果集合
    List<T> list = new ArrayList<>();
    // 获取结果集元数据
    ResultSetMetaData metaData = resultSet.getMetaData();

    // 获取类信息
    List<ClassInfo> classInfoList = getClassInfo(clazz);

    while (resultSet.next()) {
      // 根据类型反射构建对象
      T element = clazz.getDeclaredConstructor().newInstance();

      for (int i = 0; i < metaData.getColumnCount(); i++) {
        String columnLabel = metaData.getColumnLabel(i + 1);
        String value = resultSet.getString(i + 1);

        classInfoList.stream()
            .filter(classInfo -> classInfo.getMappingField().value().equals(columnLabel))
            .forEach(
                ThrowingConsumer.unchecked(
                    classInfo -> {
                      Method method = classInfo.getMethod();
                      method.invoke(element, value);
                    }));
      }

      list.add(element);
    }
    return list;
  }

  /**
   * 根据类型获取 ClassInfo
   *
   * @param clazz {@link Class}
   * @return {@link List<ClassInfo>}
   */
  @SneakyThrows
  private static <T> List<ClassInfo> getClassInfo(Class<T> clazz) {
    // BeanInfo
    BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    // 循环处理值
    return Arrays.stream(pds)
        .filter(pd -> pd.getWriteMethod() != null)
        .map(ThrowingFunction.unchecked(pd -> initClassInfo(clazz, pd)))
        .collect(Collectors.toList());
  }

  @SneakyThrows
  private static <T> ClassInfo initClassInfo(Class<T> clazz, PropertyDescriptor pd) {
    Method writeMethod = pd.getWriteMethod();
    Field field = clazz.getDeclaredField(pd.getName());
    MappingField mappingField = field.getAnnotation(MappingField.class);
    // 获取只写方法
    ClassInfo classInfo = new ClassInfo();
    classInfo.setField(field);
    classInfo.setMethod(writeMethod);
    classInfo.setMappingField(mappingField);
    return classInfo;
  }
}
